/*
 * Copyright 2016 Hannes Dorfmann.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package com.hannesdorfmann.mosby3;

import com.hannesdorfmann.mosby3.mvi.MviPresenter;
import com.hannesdorfmann.mosby3.mvp.MvpView;
import ohos.aafwk.ability.Ability;
import ohos.aafwk.ability.AbilityLifecycleCallbacks;
import ohos.agp.components.Component;
import ohos.app.Context;
import ohos.app.ElementsCallback;
import ohos.global.configuration.Configuration;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.utils.PacMap;

import java.util.UUID;

/**
 * The default implementation of {@link ComponentContainerMviDelegate}
 *
 * @author Hannes Dorfmann
 * @see ComponentContainerMviDelegate
 */
public class ComponentContainerMviDelegateImpl<V extends MvpView, P extends MviPresenter<V, ?>>
        implements ComponentContainerMviDelegate<V, P>, AbilityLifecycleCallbacks, ElementsCallback {


  // TODO allow custom save state hook in
  public final static boolean DEBUG = false;
  private static final String TAG = "ComponentContainerMviDelegateImpl";
  private static final HiLogLabel DEBUG_TAG = new HiLogLabel(HiLog.LOG_APP, 0, TAG);
  private ComponentContainerMviDelegateCallback<V, P> delegateCallback;
  private String mosbyViewId;
  private final boolean keepPresenterDuringScreenOrientationChange;
  private P presenter;
  private final Ability activity;

  private boolean checkedActivityFinishing = false;
  private boolean presenterDetached = false;
  private boolean presenterDestroeyed = false;

  public ComponentContainerMviDelegateImpl(Component view,
                                  ComponentContainerMviDelegateCallback<V, P> delegateCallback,
                                  boolean keepPresenterDuringScreenOrientationChange) {

    if (view == null) {
      throw new NullPointerException("View is null!");
    }
    if (delegateCallback == null) {
      throw new NullPointerException("MvpDelegateCallback is null!");
    }
    this.delegateCallback = delegateCallback;
    this.keepPresenterDuringScreenOrientationChange = keepPresenterDuringScreenOrientationChange;

    this.activity = PresenterManager.getActivity(delegateCallback.getmComponetContext());
    this.activity.getAbilityPackage().registerCallbacks(this, this);
  }

  /**
   * Generates the unique (mosby internal) viewState id and calls {@link
   * MviDelegateCallback#createPresenter()}
   * to create a new presenter instance
   *
   * @return The new created presenter instance
   */
  private P createViewIdAndCreatePresenter() {

    P presenter = delegateCallback.createPresenter();
    if (presenter == null) {
      throw new NullPointerException("Presenter returned from createPresenter() is null.");
    }
    if (keepPresenterDuringScreenOrientationChange) {
      Context context = delegateCallback.getmComponetContext();
      mosbyViewId = UUID.randomUUID().toString();
      PresenterManager.putPresenter(PresenterManager.getActivity(context), mosbyViewId, presenter);
    }
    return presenter;
  }


  @Override public void onAttachedToWindow() {
    boolean viewStateWillBeRestored = false;

    if (mosbyViewId == null) {
      // No presenter available,
      // Activity is starting for the first time (or keepPresenterInstance == false)
      presenter = createViewIdAndCreatePresenter();
      if (DEBUG) {
        HiLog.debug(DEBUG_TAG, "new Presenter instance created: " + presenter);
      }
    } else {
      presenter = PresenterManager.getPresenter(activity, mosbyViewId);
      if (presenter == null) {
        // Process death,
        // hence no presenter with the given viewState id stored, although we have a viewState id
        presenter = createViewIdAndCreatePresenter();
        if (DEBUG) {
          HiLog.debug(DEBUG_TAG,
                  "No Presenter instance found in cache, although MosbyView ID present. This was caused by process death, therefore new Presenter instance created: "
                          + presenter);
        }
      } else {
        viewStateWillBeRestored = true;
        if (DEBUG) {
          HiLog.debug(DEBUG_TAG, "Presenter instance reused from internal cache: " + presenter);
        }
      }
    }

    // presenter is ready, so attach viewState
    V view = delegateCallback.getMvpView();
    if (view == null) {
      throw new NullPointerException(
              "MvpView returned from getMvpView() is null. Returned by " + delegateCallback);
    }

    if (viewStateWillBeRestored) {
      delegateCallback.setRestoringViewState(true);
    }

    presenter.attachView(view);

    if (viewStateWillBeRestored) {
      delegateCallback.setRestoringViewState(false);
    }

    if (DEBUG) {
      HiLog.debug(DEBUG_TAG,
              "MvpView attached to Presenter. MvpView: " + view + "   Presenter: " + presenter);
    }
  }

  @Override public void onDetachedFromWindow() {

    detachPresenterIfNotDoneYet();

    if (!checkedActivityFinishing) {

      boolean destroyPermanently = !AbilityMviDelegateImpl.retainPresenterInstance(
              keepPresenterDuringScreenOrientationChange, activity);

      if (destroyPermanently) {
        destroyPresenterIfnotDoneYet();
      } else if (!activity.isTerminating()) {
        // View removed manually from screen
        destroyPresenterIfnotDoneYet();
      }
    } // else --> see onActivityDestroyed()
  }

  private void destroyPresenterIfnotDoneYet() {

    if (!presenterDestroeyed) {
      presenter.destroy();
      presenterDestroeyed = true;
      if (DEBUG) {
        HiLog.debug(DEBUG_TAG, "Presenter destroyed: " + presenter);
      }

      if (mosbyViewId != null) {
        // mosbyViewId == null if keepPresenterDuringScreenOrientationChange == false
        PresenterManager.remove(activity, mosbyViewId);
      }
      mosbyViewId = null;
    }
  }

  private void detachPresenterIfNotDoneYet() {

    if (!presenterDetached) {
      presenter.detachView();
      presenterDetached = true;
      if (DEBUG) {
        HiLog.debug(DEBUG_TAG,
                "view " + delegateCallback.getMvpView() + " detached from Presenter " + presenter);
      }
    }
  }

  @Override
  public void onAbilityStart(Ability ability) {

  }

  @Override
  public void onAbilityActive(Ability ability) {

  }

  @Override
  public void onAbilityInactive(Ability ability) {

  }

  @Override
  public void onAbilityForeground(Ability ability) {

  }

  @Override
  public void onAbilityBackground(Ability ability) {

  }

  @Override
  public void onAbilityStop(Ability ability) {
    if (ability == this.activity) {
      // The hosting activity of this view has been destroyed, so time to destoryed the presenter too?

      ability.getAbilityPackage().unregisterCallbacks(this, this);
      checkedActivityFinishing = true;

      boolean destroyedPermanently = !AbilityMviDelegateImpl.retainPresenterInstance(
              keepPresenterDuringScreenOrientationChange, activity);

      if (destroyedPermanently) {
        // Whole activity will be destroyed
        detachPresenterIfNotDoneYet();
        destroyPresenterIfnotDoneYet();
      }
    }
  }

  @Override
  public void onAbilitySaveState(PacMap pacMap) {

  }

  @Override
  public void onMemoryLevel(int i) {

  }

  @Override
  public void onConfigurationUpdated(Configuration configuration) {

  }
}
